using System;
using System.Collections.Generic;
using CoreLibrary;
using CoreLibrary.Cache;
using CoreLibrary.Email;
using CoreLibrary.Email.Impl;
using CoreLibrary.Service;
using CoreLibrary.Sql.Linq;
using Entities.DataBase;
using Entities.DataBase.Impl;
using Modules.API;
using Modules.Tracking;
using NLog;
using Ninject;
using WebService.Config;

namespace WebService {

	public class Services {

		private volatile static StandardKernel _kernel;
		private static readonly List<IService> All = new List<IService>();

		private static void InitServices() {
            Failover(InitCache);
            Failover(InitConecctionProvider);

            Asserts.NotNull(Locate<ICache>(), "Cache is required.");
            Asserts.NotNull(Locate<ILinqConnectionProvider>(),  "Connection Provider can not be null.");

            Failover(InitCurrencyExchangeService);
		    Failover(InitLogService);
            Failover(InitGuid);
            Failover(InitEmailService);
            Failover(InitSettingRepository);
            Failover(InitAccountRepository);
            Failover(InitPackageRepository);
            Failover(InitProductCatalogRepository);
            Failover(InitProductRepository);
		    Failover(InitPurchaseRecordRepository);
            Failover(InitOrderRepository);
            Failover(InitOrderRecordRepository);
		    Failover(InitProductStockRepository);
            Failover(InitCostRepository);
		    Failover(InitCustomerRepository);
            Failover(InitShipmentService);
		    

            Asserts.NotNull(Locate<IntGuidRepository>(),         "Guid Repository is required.");
            Asserts.NotNull(Locate<ISettingRepository>(),        "Setting Repository is required.");
            Asserts.NotNull(Locate<ICostRepository>(),           "Cost Repository is required.");
            Asserts.NotNull(Locate<IPackageRepository>(),        "Package Repository is required.");
            Asserts.NotNull(Locate<IProductCatalogRepository>(), "Product Catalog Repository is required.");
            Asserts.NotNull(Locate<IProductRepository>(),        "Product Repository is required.");
            Asserts.NotNull(Locate<IOrderRepository>(),          "Order Repository is required.");
            Asserts.NotNull(Locate<ICustomerRepository>(),       "Customer Repository is required.");
           
		}

	    private static ILinqConnectionProvider InitConecctionProvider() {
	        return InjectAndStart<ILinqConnectionProvider, LinqConnectionProvider> (
                   new LinqConnectionProvider {
                       ConnectionName   = "DatabaseConnectionString",
                       ConnectionString = AppSettings.ConnectionStrings
	               }
            );
	    }

	    private static ICache InitCache() {
	        return InjectAndStart<ICache, EntLibCache>(new EntLibCache());
	    }

        private static IIntGuidRepository InitGuid() {
            return InjectAndStart<IIntGuidRepository, IntGuidRepository>(
                new IntGuidRepository {
                    Connection = Locate<ILinqConnectionProvider>()
                }
            );
        }

        private static ICurrencyExchangeService InitCurrencyExchangeService () {
            return InjectAndStart<ICurrencyExchangeService, CurrencyExchangeService>(
                new CurrencyExchangeService());
        }

        private static IEmailService InitEmailService() {
            return InjectAndStart<IEmailService, MandrillEmailService>(
                new MandrillEmailService {
                    Configuration = AppSettings.MandrillConfig
                }
            );
        }

        private static ILogService InitLogService() {
            return InjectAndStart<ILogService, LogService>(
                new LogService {
                    Connection = Locate<ILinqConnectionProvider>()
                }
            );
        }

	    private static IUserRepository InitAccountRepository() {
	        return InjectAndStart<IUserRepository, UserRepository>(
                new UserRepository {
                    Connection = Locate<ILinqConnectionProvider >()
                }
            );
	    }

        private static ISettingRepository InitSettingRepository() {
            return InjectAndStart<ISettingRepository, SettingRepository>(
                new SettingRepository {
                    Cache = Locate<ICache>(),
                    Connection = Locate<ILinqConnectionProvider>()
                }
            );
        }

        private static IPackageRepository InitPackageRepository() {
            return InjectAndStart<IPackageRepository, PackageRepository>(
                new PackageRepository {
                    Connection = Locate<ILinqConnectionProvider>()
                }
            );
        }

        private static IOrderRepository InitOrderRepository() {
            return InjectAndStart<IOrderRepository, OrderRepository>(
                new OrderRepository {
                    Connection   = Locate<ILinqConnectionProvider>(),
                }
            );
        }

        private static IOrderRecordRepository InitOrderRecordRepository() {
            return InjectAndStart<IOrderRecordRepository, OrderRecordRepository>(
                new OrderRecordRepository {
                    Connection = Locate<ILinqConnectionProvider>()
                }
            );
        }

	    private static IProductCatalogRepository InitProductCatalogRepository() {
            return InjectAndStart<IProductCatalogRepository, ProductCatalogRepository>(
                new ProductCatalogRepository {
                    Connection = Locate<ILinqConnectionProvider>()
                }
            );
        }

        private static IPurchaseRecordRepository InitPurchaseRecordRepository() {
            return InjectAndStart<IPurchaseRecordRepository, PurchaseRecordRepository>(
                new PurchaseRecordRepository {
                    Connection = Locate<ILinqConnectionProvider>()
                }
            );
        }

        private static IProductRepository InitProductRepository() {
            return InjectAndStart<IProductRepository, ProductRepository>(
                new ProductRepository {
                    Connection = Locate<ILinqConnectionProvider>()
                }
            );
        }

	    private static IProductStockRepository InitProductStockRepository() {
            return InjectAndStart<IProductStockRepository,ProductStockRepository>(
                new ProductStockRepository {
                    Connection      = Locate<ILinqConnectionProvider>(),
                    Orders          = Locate<IOrderRepository>(),
                    OrderRecords    = Locate<IOrderRecordRepository>(),
                    PurchaseRecords = Locate<IPurchaseRecordRepository>()
                }
            );
        }

	    private static ICustomerRepository InitCustomerRepository() {
            return InjectAndStart<ICustomerRepository, CustomerRepository>(
                new CustomerRepository {
                    Connection = Locate<ILinqConnectionProvider>()
                }
            );
        }

	    private static ICostRepository InitCostRepository() {
            return InjectAndStart<ICostRepository, CostRepository>(
                new CostRepository {
                   Connection =  Locate<ILinqConnectionProvider>()
                }
            );
        }

	    private static IShipmentService InitShipmentService() {
            return InjectAndStart<IShipmentService, ShipmentService>(
                new ShipmentService {
                }
            );
        }

	    public static T Locate<T>() where T : class {
			var service = GetKernel().Get<T>();
			if (service == null) {
				throw new ArgumentException("Not found instance of " + typeof(T).Name);
			}
			return service;
		}

		public static T Locate<T>(string name) where T : class {
			var service = GetKernel().Get<T>(name);
			if (service == null) {
				throw new ArgumentException("Not found instance of " + typeof(T).Name + " named '" + name + "'");
			}
			return service;
		}

		private static T Failover<T>(Func<T> func) where T : class, IService {
			try {
				return func();
			}
			catch (Exception e) {
				LogManager.GetCurrentClassLogger().ErrorException("Unexpected failure: " + e.Message, e);
				return null;
			}
		}

		private static TFrom InjectAndStart<TFrom, TTo>(TTo service)
			where TTo : TFrom
			where TFrom : IService {
			var serviceImpl = Inject<TFrom, TTo>(service);
			if (serviceImpl.ServiceState.Code == ServiceState.Initialized) {
				serviceImpl.Start();
			}
			return serviceImpl;
		}

		private static TFrom Inject<TFrom, TTo>(TTo service)
			where TTo : TFrom
			where TFrom : IService {
			All.Add(service);
			service.Name = service.GetType().Name;
			service.Init();
			if (service.ServiceState.Code == ServiceState.Initialized) {
				_kernel.Bind<TFrom>()
					   .ToConstant(service)
					   .InSingletonScope();
			}
			return service;
		}

		private static TFrom InjectWithName<TFrom, TTo>(string name, TTo service)
			where TTo : TFrom
			where TFrom : IService {
			All.Add(service);
			service.Name = service.GetType().Name;
			service.Init();
			if (service.ServiceState.Code == ServiceState.Initialized) {
				_kernel.Bind<TFrom>()
					   .ToConstant(service)
					   .InSingletonScope()
					   .Named(name);
			}
			return service;
		}

		private static StandardKernel GetKernel(){
			if (_kernel == null) {
				lock (All) {
					if (_kernel == null) {
						try {
							_kernel = new StandardKernel();
							InitServices();
							LogManager.GetCurrentClassLogger().Info("Services Started");
						} 
                        catch (Exception error) {
							LogManager.GetCurrentClassLogger().ErrorException("Services failed to start: " + error.Message, error);
						}
					}
				}
			}
			return _kernel;
		}

	}
}